package cn.zhiquxing.lock;

import lombok.Data;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * CLH lock
 */
public class CLHLock implements Lock {
    /*当前节点的线程本地变量 */
    private static ThreadLocal<Node> curNodeLocal = new ThreadLocal<>();
    /*clhlock队尾的指针 */
    private AtomicReference<Node> tail = new AtomicReference<>(null);

    public CLHLock() {
        tail.getAndSet(Node.EMPTY);
    }
    
    // 加锁操作，将节点添加到等待队列的尾部
    @Override
    public void lock() {
        Node curNode = new Node(true, null);
        Node preNode = tail.get();
        // cas自旋：将当前节点添加到等待队列的尾部
        while (!tail.compareAndSet(preNode, curNode)) {
            preNode = tail.get();
        }
        // 设置前驱点
        curNode.setPrevNode(preNode);
        //自旋，监听前驱点的lock变量，直到其值为false
        // 若前驱点的lock状态为true，表示前一个线程还在抢占或占有锁
        while (curNode.getPrevNode().isLocked()) {
            Thread.yield();
        }
        // 执行到这里，说明线程获取到了锁
        System.out.println(Thread.currentThread().getName() + "：获取到了锁");
        //将当前节点缓存到线程本地变量中，释放会用到
        curNodeLocal.set(curNode);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        Node curNode = curNodeLocal.get();
        curNode.setLocked(false);
        curNode.setPrevNode(null);
        curNodeLocal.set(null);
    }

    @NotNull
    @Override
    public Condition newCondition() {
        return null;
    }
    
    @Data
    static class Node{
        // true当前线程正在抢占锁，或者已经占用锁
        // false 当前线程已将释放锁，下一个线程可以占用锁
        volatile boolean locked;
        // 前一个节点，需要监听其lock字段
        Node prevNode;
        public Node(boolean locked, Node prevNode) {
            this.locked = locked;
            this.prevNode = prevNode;
        }
        
        public static final Node EMPTY = new Node(false, null);
    }
}
